package me.ycy.android.shuffle.synchronizer
import org.dodgybits.shuffle.android.persistence.provider._
import java.util.Date
import android.content.{ ContentResolver, ContentValues }
import me.ycy.android.helper.ContentValuesWrapper
import android.net.Uri
import android.util.Log

object Shuffle {
  val Tag = "ShuffleSync"

  trait CommonColumns {
    import AbstractCollectionProvider.ShuffleTable._

    val ModifiedDate = MODIFIED_DATE
    val TracksId = TRACKS_ID
    val Deleted = DELETED
    val Id = android.provider.BaseColumns._ID
    val ProjectId = TaskProvider.Tasks.PROJECT_ID
    val ContextId = TaskProvider.Tasks.CONTEXT_ID
    val Active = ACTIVE
  }

  object CommonColumns extends CommonColumns

  trait Provider extends CommonColumns {
    val uri: android.net.Uri
    val projection: Array[String]
    val types: List[Class[_]]
    def toItem(m: Map[String, FieldType[_]]): ektorp.ShuffleItem

    assert(types.size == projection.size)
  }

  object Tasks extends {
    val uri = TaskProvider.Tasks.CONTENT_URI
    val projection = TaskProvider.Tasks.FULL_PROJECTION
    val types = List(classOf[Long], // _ID
                     classOf[String], // DESCRIPTION
                     classOf[String], // DETAILS
                     classOf[Long], // PROJECT_ID
                     classOf[Long], // CONTEXT_ID
                     classOf[Date], // CREATED_DATE
                     classOf[Date], // MODIFIED_DATE
                     classOf[Date], // START_DATE
                     classOf[Date], // DUE_DATE
                     classOf[String], // TIMEZONE
                     classOf[Long], // CAL_EVENT_ID
                     classOf[Int], // DISPLAY_ORDER
                     classOf[Boolean], // COMPLETE
                     classOf[Boolean], // ALL_DAY
                     classOf[Boolean], // HAS_ALARM
                     classOf[Long], // TRACKS_ID
                     classOf[Boolean], // DELETED
                     classOf[Boolean]  // ACTIVE
                   )
  } with Provider {
    // NOTE: assume all FK ids are already replaced with couchdb _id
    def toItem(m: Map[String, FieldType[_]]): ektorp.Task = {
      val task = new ektorp.Task
      task.id = m(Tasks.TracksId).as[Long]
      task.description = m(TaskProvider.Tasks.DESCRIPTION).as[String]
      task.details = m(TaskProvider.Tasks.DETAILS).as[String]
      task.projectId = m(TaskProvider.Tasks.PROJECT_ID).as[Long]
      task.contextId = m(TaskProvider.Tasks.CONTEXT_ID).as[Long]
      task.createdDate = m(TaskProvider.Tasks.CREATED_DATE).as[Date]
      task.modifiedDate = m(Tasks.ModifiedDate).as[Date]
      task.startDate = m(TaskProvider.Tasks.START_DATE).as[Date]
      task.dueDate = m(TaskProvider.Tasks.DUE_DATE).as[Date]
      task.timezone = m(TaskProvider.Tasks.TIMEZONE).as[String]
      task.calEventId = m(TaskProvider.Tasks.CAL_EVENT_ID).as[Long]
      task.displayOrder = m(TaskProvider.Tasks.DISPLAY_ORDER).as[Int]
      task.complete = m(TaskProvider.Tasks.COMPLETE).as[Boolean]
      task.allDay =  m(TaskProvider.Tasks.ALL_DAY).as[Boolean]
      task.hasAlarm = m(TaskProvider.Tasks.HAS_ALARM).as[Boolean]
      task.deleted = m(Tasks.Deleted).as[Boolean]
      task.active = m(Tasks.Active).as[Boolean]

      task
    }
  }

  object Projects extends {
    val uri = ProjectProvider.Projects.CONTENT_URI
    val projection = ProjectProvider.Projects.FULL_PROJECTION
    val types = List(classOf[Long], // _ID
                     classOf[String], // NAME
                     classOf[Long], // DEFAULT_CONTEXT_ID
                     classOf[Long], // TRACKS_ID
                     classOf[Date], // MODIFIED_DATE
                     classOf[Boolean], // PARALLEL
                     classOf[Boolean], // ARCHIVED
                     classOf[Boolean], // DELETED
                     classOf[Boolean] // ACTIVE
                   )
  } with Provider {
    def toItem(m: Map[String, FieldType[_]]): ektorp.Project = {
      val project = new ektorp.Project

      project.id = m(Projects.TracksId).as[Long]
      project.name = m(ProjectProvider.Projects.NAME).as[String]
      project.defaultContextId = m(ProjectProvider.Projects.DEFAULT_CONTEXT_ID).as[Long]
      project.modifiedDate = m(Projects.ModifiedDate).as[Date]
      project.parallel = m(ProjectProvider.Projects.PARALLEL).as[Boolean]
      project.archived = m(ProjectProvider.Projects.ARCHIVED).as[Boolean]
      project.deleted = m(Projects.Deleted).as[Boolean]
      project.active = m(Projects.Active).as[Boolean]

      project
    }
  }

  object Contexts extends {
    val uri = ContextProvider.Contexts.CONTENT_URI
    val projection = ContextProvider.Contexts.FULL_PROJECTION
    val types = List(classOf[Long], // _ID,
                     classOf[String], // NAME,
		     classOf[Int], // COLOUR,
		     classOf[String], // ICON,
		     classOf[Long], // TRACKS_ID,
		     classOf[Date], // MODIFIED_DATE,
		     classOf[Boolean], // DELETED,
		     classOf[Boolean] // ACTIVE
                   )
  } with Provider {
    def toItem(m: Map[String, FieldType[_]]): ektorp.Context = {
      val context = new ektorp.Context

      context.id = m(Contexts.TracksId).as[Long]
      context.name = m(ContextProvider.Contexts.NAME).as[String]
      context.colour = m(ContextProvider.Contexts.COLOUR).as[Int]
      context.icon = m(ContextProvider.Contexts.ICON).as[String]
      context.modifiedDate = m(Contexts.ModifiedDate).as[Date]
      context.deleted = m(Contexts.Deleted).as[Boolean]
      context.active = m(Contexts.Active).as[Boolean]

      context
    }
  }

  // convert shuffle item to ContentValues
  def itemToContentValues(item: ektorp.ShuffleItem): ContentValues = {
    val w: ContentValuesWrapper = new ContentValuesWrapper
    item match {
      case task: ektorp.Task => {
        // '\([^. ]+.[^.]+?\) *= *m(\([^)]+\)).as\[\(.*\)\]' => w.put\3(\2, \1)
        w.putLong(Tasks.TracksId, task.id)
        w.putString(TaskProvider.Tasks.DESCRIPTION, task.description)
        w.putString(TaskProvider.Tasks.DETAILS, task.details)
        w.putLongOrNull(TaskProvider.Tasks.PROJECT_ID, task.projectId)
        w.putLongOrNull(TaskProvider.Tasks.CONTEXT_ID, task.contextId)
        w.putLong(TaskProvider.Tasks.CREATED_DATE, task.createdDate.getTime)
        w.putLong(Tasks.ModifiedDate, task.modifiedDate.getTime)
        w.putLong(TaskProvider.Tasks.START_DATE, task.startDate.getTime)
        w.putLong(TaskProvider.Tasks.DUE_DATE, task.dueDate.getTime)
        w.putString(TaskProvider.Tasks.TIMEZONE, task.timezone)
        w.putLongOrNull(TaskProvider.Tasks.CAL_EVENT_ID, task.calEventId)
        w.putInteger(TaskProvider.Tasks.DISPLAY_ORDER, task.displayOrder)
        w.putBoolean(TaskProvider.Tasks.COMPLETE, task.complete)
        w.putBoolean(TaskProvider.Tasks.ALL_DAY, task.allDay)
        w.putBoolean(TaskProvider.Tasks.HAS_ALARM, task.hasAlarm)
        w.putBoolean(Tasks.Deleted, task.deleted)
        w.putBoolean(Tasks.Active, task.active)
      }
      case project: ektorp.Project => {
        w.putLong(Projects.TracksId, project.id)
        w.putString(ProjectProvider.Projects.NAME, project.name)
        w.putLongOrNull(ProjectProvider.Projects.DEFAULT_CONTEXT_ID, project.defaultContextId)
        w.putLong(Projects.ModifiedDate, project.modifiedDate.getTime)
        w.putBoolean(ProjectProvider.Projects.PARALLEL, project.parallel)
        w.putBoolean(ProjectProvider.Projects.ARCHIVED, project.archived)
        w.putBoolean(Projects.Deleted, project.deleted)
        w.putBoolean(Projects.Active, project.active)
      }
      case context: ektorp.Context => {
        w.putLong(Contexts.TracksId, context.id)
        w.putString(ContextProvider.Contexts.NAME, context.name)
        w.putInteger(ContextProvider.Contexts.COLOUR, context.colour)
        w.putString(ContextProvider.Contexts.ICON, context.icon)
        w.putLong(Contexts.ModifiedDate, context.modifiedDate.getTime)
        w.putBoolean(Contexts.Deleted, context.deleted)
        w.putBoolean(Contexts.Active, context.active)
      }
      case _ =>
        throw new Exception("unsupport type: " + item.getClass)
    }
    w.android
  }

  // NOTE: current impl can't detect conflict, but with realtime sync conflict
  //       will rare happen
  def storeItem(item: ektorp.ShuffleItem, cr: ContentResolver) = {
    import Helper._

    val rId = item.id

    // find shuffle record
    val provider = item match {
      case _: ektorp.Task => Tasks
      case _: ektorp.Project => Projects
      case _: ektorp.Context => Contexts
      case _ => throw new Exception("item type not support: " + item.getClass)
    }

    Log.d(Tag, "store item uri " + provider.uri)
    // foreign key translation
    item match {
      case i: ektorp.Project => if (i.defaultContextId != 0) {
        val p = Shuffle.Contexts
        i.defaultContextId =
          withCursor(cr.query(p.uri, Array(p.Id),
                              p.TracksId + " = ?",
                              Array(i.defaultContextId.toString),
                              null)) { c1 => {
          if (c1.moveToFirst) {
            c1.getLong(0)
          }
          else { // can't find id, using zero
            Log.w(Tag, "Can't find defaultContextId _ID for project " + i.name)
            0
          }
        }}
      }
      case i: ektorp.Task => {
        if (i.contextId != 0) {
          val p = Shuffle.Contexts
          i.contextId =
            withCursor(cr.query(p.uri, Array(p.Id),
                                p.TracksId + " = ?",
                                Array(i.contextId.toString),
                                null)) { c1 => {
              if (c1.moveToFirst) {
                c1.getLong(0)
              }
              else { // can't find id, using zero
                Log.w(Tag, "Can't find contextId _ID for task " + i.description)
                0
              }
            }}
        }

        if (i.projectId != 0) {
          val p = Shuffle.Projects
          i.projectId =
            withCursor(cr.query(p.uri, Array(p.Id),
                                p.TracksId + " = ?",
                                Array(i.projectId.toString),
                                null)) { c1 => {
              if (c1.moveToFirst) {
                c1.getLong(0)
              }
              else { // can't find id, using zero
                Log.w(Tag, "Can't find projectId _ID for task " + i.description)
                0
              }
            }}
        }
      }
      case _ =>
    }


    Helper.withCursor(cr.query(provider.uri, Array(CommonColumns.ModifiedDate),
                        "? = " + CommonColumns.TracksId, Array(rId.toString),
                        CommonColumns.ModifiedDate + " DESC")) { cur => {
      val cnt = cur.getCount()
      val cv = itemToContentValues(item)
      if (cnt == 0) {
        Log.i(Tag, "new item: " + item)
        cr.insert(provider.uri, cv)
      }
      else {
        if (cnt > 1) {
          // NOTE: will update all records, only use first
          Log.w(Tag, "multiple(" + cnt + ") record with same remote Id: " + rId)
        }
        cur.moveToFirst
        val localTime = cur.getLong(0)
        if (localTime < item.modifiedDate.getTime) {
          // update
          Log.i(Tag, "update item, " + item)
          cr.update(provider.uri, cv,
                    "? = " + CommonColumns.TracksId, Array(rId.toString))
        }
        else {
          Log.i(Tag, "skip, remote item(" + item.modifiedDate + ") is not newer than local (" +
                new Date(localTime) + ").")
        }
      }
    }}
  }
}

